using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Sudoku.Utilities;

namespace Sudoku.GamePlay
{
	public enum Difficulty
	{
		Beginner,
		Normal,
		Experienced,
		Extreme
	}

	public struct Score
	{
		public Difficulty Difficulty;
		public int Hints;
		public float Time;
	}

	public interface IBoardCreator
	{
		int NumberOfEvaluatedSudoku();
	}

	public class BoardCreator : GameComponent, IBoardCreator
	{
		Solver solver;

		Score score;
		BoardCell[,] grid = new BoardCell[9, 9];
		BoardCell[,] playerGrid = new BoardCell[9, 9];

		BoardCell selected = null;
		short leftCellsCount;
		short startingHints;

		Random rnd;

		int sudokuChecked;
		float timeToWait;
		float timeWaited;

		public BoardCreator(Game game, Difficulty difficulty)
			: base(game)
		{
			score.Difficulty = difficulty;
			score.Hints = 0;
			score.Time = 0.0f;
			leftCellsCount = 81;

			solver = new Solver();
			rnd = new Random(DateTime.Now.Millisecond);

			Game.Services.AddService(typeof(IBoardCreator), this);

			Game.Activated += new EventHandler<EventArgs>(OnActivated);
			Game.Deactivated += new EventHandler<EventArgs>(OnDeactivated);
			Game.Exiting += new EventHandler<EventArgs>(OnDeactivated);

			startingHints = GetStartingHints(score.Difficulty);

			sudokuChecked = 0;
			timeToWait = 1.0f;
			timeWaited = 0.0f;
		}

		protected override void Dispose(bool disposing)
		{
			Game.Services.RemoveService(typeof(IBoardCreator));
			base.Dispose(disposing); 
			
			Game.Activated -= new EventHandler<EventArgs>(OnActivated);
			Game.Deactivated -= new EventHandler<EventArgs>(OnDeactivated);
			Game.Exiting -= new EventHandler<EventArgs>(OnDeactivated);
		}

		#region PHONE EVENTS

		private void OnActivated(object sender, EventArgs args)
		{
			this.Enabled = true;
		}

		private void OnDeactivated(object sender, EventArgs args)
		{
			//if it's disabled, don't save anything.
			if (!this.Enabled) return;

			this.Enabled = false;
		}

		#endregion

		#region SUDOKU CREATION

		private void RestoreGrids()
		{
			grid = new BoardCell[,]{
				{ 1, 2, 3, 4, 5, 6, 7, 8, 9 },
				{ 4, 5, 6, 7, 8, 9, 1, 2, 3 },
				{ 7, 8, 9, 1, 2, 3, 4, 5, 6 },
				{ 2, 3, 4, 5, 6, 7, 8, 9, 1 },
				{ 5, 6, 7, 8, 9, 1, 2, 3, 4 },
				{ 8, 9, 1, 2, 3, 4, 5, 6, 7 },
				{ 3, 4, 5, 6, 7, 8, 9, 1, 2	},
				{ 6, 7, 8, 9, 1, 2, 3, 4, 5 },
				{ 9, 1, 2, 3, 4, 5, 6, 7, 8 }
			};

			playerGrid = new BoardCell[,]{
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0},
				{0, 0, 0, 0, 0, 0, 0, 0, 0}
			};
		}

		private void CreateSudoku()
		{
			RestoreGrids();

			int numberOps = rnd.Next(Settings.MinRandomization, Settings.MaxRandomization);

			for (int i = 0; i < numberOps; ++i)
			{
				int opType = rnd.Next(0, 6);

				switch (opType)
				{
					//Swap row between a band
					case 0:
						SwapRows(rnd.Next(0, 3), rnd.Next(0, 3), rnd.Next(0, 3));
						break;

					//Swap col between a stack
					case 1:
						SwapCols(rnd.Next(0, 3), rnd.Next(0, 3), rnd.Next(0, 3));
						break;

					//Symbol permutation
					case 2:
						PermuteSymbols((byte)rnd.Next(1, 10), (byte)rnd.Next(1, 10));
						break;

					//Transposition
					case 3:
						Transpose(rnd.Next(0, 2) > 0);
						break;

					//Swap Bands
					case 4:
						SwapBands(rnd.Next(0, 3), rnd.Next(0, 3));
						break;

					//Swap Stacks
					case 5:
						SwapStacks(rnd.Next(0, 3), rnd.Next(0, 3));
						break;
				}
			}

			short startNoAmount = startingHints;
			leftCellsCount = 0;

			//clone my grid..
			playerGrid = new BoardCell[9, 9];
			for (int i = 0; i < 9; ++i)
			{
				for (int j = 0; j < 9; ++j)
				{
					playerGrid[i, j] = new BoardCell(grid[i, j].Value);
				}
			}

			while (startNoAmount > 0)
			{
				RemoveSymPair();
				startNoAmount -= 2;
			}
		}

		private bool IsSudokuReady(int diff)
		{
			bool isOk = false;
			switch (score.Difficulty)
			{
				case Difficulty.Beginner:
					if (diff >= 42 && diff <= 46)
						isOk = true;
					break;
				case Difficulty.Normal:
					if (diff >= 49 && diff <= 53)
						isOk = true;
					break;
				case Difficulty.Experienced:
					if (diff >= 56 && diff <= 60)
						isOk = true;
					break;
				case Difficulty.Extreme:
					if (diff >= 61 && diff <= 116)
						isOk = true;
					break;
			}

			return isOk;
		}

		private void RemoveSymPair()
		{
			int x = rnd.Next(0, 9);
			int y = rnd.Next(0, 9);
			while (playerGrid[x, y].Value == 0)
			{
				x = rnd.Next(0, 9);
				y = rnd.Next(0, 9);
			}

			playerGrid[x, y].Value = 0;
			playerGrid[8 - x, 8 - y] = 0;
		}

		private void SwapSymPair()
		{
			int x = rnd.Next(0, 9);
			int y = rnd.Next(0, 9);
			while (playerGrid[x, y].Value != 0)
			{
				x = rnd.Next(0, 9);
				y = rnd.Next(0, 9);
			}

			playerGrid[x, y] = new BoardCell(grid[x,y]);
			playerGrid[8 - x, 8 - y] = new BoardCell(grid[8 - x, 8 - y]);

			RemoveSymPair();
		}

		private void SwapRows(int bandNo, int first, int second)
		{
			for (int i = 0; i < 9; ++i)
			{
				byte t = grid[bandNo * 3 + first, i].Value;
				grid[bandNo * 3 + first, i].Value = grid[bandNo * 3 + second, i].Value;
				grid[bandNo * 3 + second, i].Value = t;
			}
		}

		private void SwapCols(int bandNo, int first, int second)
		{
			for (int i = 0; i < 9; ++i)
			{
				byte t = grid[i, bandNo * 3 + first].Value;
				grid[i, bandNo * 3 + first].Value = grid[i, bandNo * 3 + second].Value;
				grid[i, bandNo * 3 + second].Value = t;
			}
		}

		private void PermuteSymbols(byte first, byte second)
		{
			for (int y = 0; y < 9; ++y)
			{
				for (int x = 0; x < 9; ++x)
				{
					if (grid[x, y].Value == first) grid[x, y].Value = second;
					else if (grid[x, y].Value == second) grid[x, y].Value = first;

				}
			}
		}

		private void Transpose(bool side)
		{
			if (side)
			{
				for (int y = 0; y < 9; ++y)
				{
					for (int x = y + 1; x < 9; ++x)
					{
						byte t = grid[x, y].Value;
						grid[x, y].Value = grid[y, x].Value;
						grid[y, x].Value = t;
					}
				}
			}
			else
			{
				for (int y = 0; y < 9; ++y)
				{
					for (int x = 0; x < 9 - y; ++x)
					{
						byte t = grid[x, y].Value;
						grid[x, y].Value = grid[8 - y, 8 - x].Value;
						grid[8 - y, 8 - x].Value = t;
					}
				}
			}

		}

		private void SwapBands(int first, int second)
		{
			for (int i = 0; i < 9; ++i)
			{
				byte t1 = grid[first * 3 + 0, i].Value;
				byte t2 = grid[first * 3 + 1, i].Value;
				byte t3 = grid[first * 3 + 2, i].Value;
				grid[first * 3 + 0, i].Value = grid[second * 3 + 0, i].Value;
				grid[first * 3 + 1, i].Value = grid[second * 3 + 1, i].Value;
				grid[first * 3 + 2, i].Value = grid[second * 3 + 2, i].Value;
				grid[second * 3 + 0, i].Value = t1;
				grid[second * 3 + 1, i].Value = t2;
				grid[second * 3 + 2, i].Value = t3;
			}
		}

		private void SwapStacks(int first, int second)
		{
			for (int i = 0; i < 9; ++i)
			{
				byte t1 = grid[i, first * 3 + 0].Value;
				byte t2 = grid[i, first * 3 + 1].Value;
				byte t3 = grid[i, first * 3 + 2].Value;
				grid[i, first * 3 + 0].Value = grid[i, second * 3 + 0].Value;
				grid[i, first * 3 + 1].Value = grid[i, second * 3 + 1].Value;
				grid[i, first * 3 + 2].Value = grid[i, second * 3 + 2].Value;
				grid[i, second * 3 + 0].Value = t1;
				grid[i, second * 3 + 1].Value = t2;
				grid[i, second * 3 + 2].Value = t3;
			}
		}

		private short GetStartingHints(Difficulty diff)
		{
			short i = 0;

			switch (diff)
			{
				case Difficulty.Beginner:
					i = (short)(40 + rnd.Next(0, 6));
					break;
				case Difficulty.Normal:
					i = (short)(46 + rnd.Next(0, 5));
					break;
				case Difficulty.Experienced:
					i = (short)(50 + rnd.Next(0, 4));
					break;
				case Difficulty.Extreme:
					i = (short)(54 + rnd.Next(0, 5));
					break;
			}

			return i;
		}

		#endregion

		#region INTERFACE

		public int NumberOfEvaluatedSudoku()
		{
			return sudokuChecked;
		}

		#endregion

		private BoardSaveState SetupFoundSudoku()
		{
			BoardSaveState saveState = new BoardSaveState();

			for (int i = 0; i < 9; ++i)
			{
				for (int j = 0; j < 9; ++j)
				{
					if (playerGrid[i, j].Value == 0)
					{
						leftCellsCount++;
						//leftCellsCount = 1;
					}
					else
					{
						playerGrid[i, j].Fixed = true;
					}

					saveState.grid[i * 9 + j] = new BoardSaveState.BoardCellSaveState();
					saveState.grid[i * 9 + j].Value = grid[i, j].Value;
					for (int k = 0; k < 9; ++k) saveState.grid[i * 9 + j].Guess[k] = grid[i, j].Guess[k];
					saveState.grid[i * 9 + j].Fixed = grid[i, j].Fixed;

					saveState.playerGrid[i * 9 + j] = new BoardSaveState.BoardCellSaveState();
					saveState.playerGrid[i * 9 + j].Value = playerGrid[i, j].Value;
					for (int k = 0; k < 9; ++k) saveState.playerGrid[i * 9 + j].Guess[k] = playerGrid[i, j].Guess[k];
					saveState.playerGrid[i * 9 + j].Fixed = playerGrid[i, j].Fixed;
				}
			}

			saveState.leftCellsCount = leftCellsCount;
			saveState.startingHints = startingHints;
			saveState.score = score;

			return saveState;
		}

		public override void Update(GameTime gameTime)
		{
			timeWaited += (gameTime.ElapsedGameTime.Milliseconds/1000.0f);

			if (timeWaited > timeToWait)
			{
				int counter = rnd.Next(2, 5);
				CreateSudoku();

				for (int i = 0; i < counter; ++i)
				{
					int diff = solver.SolveGrid(playerGrid);

					if (IsSudokuReady(diff))
					{
						BoardSaveState newSudoku = SetupFoundSudoku();
						StateManagement.StateManager.ContinueGame(Game, newSudoku);
						break;
					}
					else
					{
						SwapSymPair();
					}
				}

				sudokuChecked += counter; 
			}

			base.Update(gameTime);
		}
	}
}
